/*-
 * Copyright (c) 2009-2010 David Forsythe.
 * See LICENSE file for license details.
 */

#include <ctype.h>
#include <err.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

#include "internal.h"
#include "freebsd_plist.h"

#include "pkg.h"

/* 
 * Standard Plist commands in pkg format revision 1.1.
 */
#define CMD_CD "cd"
#define CMD_COMMENT "comment"
#define CMD_CONFLICTS "conflicts"
#define CMD_CWD "cwd"
#define CMD_DIRRM "dirrm"
#define CMD_DISPLAY "display"
#define CMD_EXEC "exec"
#define CMD_GROUP "group"
#define CMD_IGNORE "ignore"
#define CMD_IGNORE_INST "ignore_inst"
#define CMD_MODE "mode"
#define CMD_MTREE "mtree"
#define CMD_NAME "name"
#define CMD_NOINST "noinst"
#define CMD_OPTION "option"
#define CMD_OWNER "owner"
#define CMD_PKGDEP "pkgdep"
#define CMD_PKGVERSION "pkgversion"
#define CMD_SRCDIR "srcdir"
#define CMD_UNEXEC "unexec"
/*
* XXX: We're leaving all 'fat' package related code until we have a spec on
* how that will work.
*/

/* 
 * Undocumented Plist commands.  Support these just because they're being used
 * in packages out now.  But realistically, either get these documented or just
 * drop them. 
 */
#define PLIST_CMD_DIRRMTRY		"dirrmtry"
#define PLIST_CMD_STOPDAEMON		"stopdaemon"
#define PLIST_CMD_RMTRY			"rmtry"

/* Plist meta comments. */
/* I really don't like having the colons on these, but I guess if the format
 * changes they'd just become commands anyway. */
#define CMT_PKG_FORMAT_REVISION "PKG_FORMAT_REVISION:"
#define CMT_ORIGIN "ORIGIN:"
#define CMT_DEPORIGIN "DEPORIGIN:"
#define CMT_MD5 "MD5:"

/* Plist options. */
/* TODO: Parse doesn't handle these. */
#define PLIST_OPTION_EXTRACT_IN_PLACE	"extract-in-place"
#define PLIST_OPTION_PRESERVE 		"preserve"

/* XXX: Stuff it would be nice to have.. */
/* Dependency version. */
#define PLIST_COMMENT_DEPVERSION	"DEPVERSION"
/* Conflict version. */
#define PLIST_COMMENT_CONVERSION	"CONVERSION"
#define PLIST_CMD_INSTALL_DATA          "installdate"
#define PLIST_OPTION_COMPLETE           "complete"


/* Etc. */
#define CMD_PREFIX '@'
#define CWD_DEFAULT "."

enum {
        PLIST_CONFLICT,
        PLIST_DEPENDENCY,
        PLIST_FILE,
        PLIST_NONE
};

/* 
 * XXX: TODO: Replace macros for property creation and setting with code that
 * actually checks return value.
 * 
 * XXX: TODO: Report parse errors more clearly.  Possibly put an ERROR property
 * into the plist?
 *
 * XXX: TODO: This code is too slow, there are definately some ways to make it 
 * faster, so do that, eventually.
 */

struct plist_parse_state {
        struct pkg_property *current_p; /* Whatever node we're building. */
        struct pkg_property *conflict_list;
        char cwd[PATH_MAX];
        struct pkg_property *depend_list;
        struct pkg_property *exec_list;
        struct pkg_property *file_list;
        int ignore;
        unsigned int last;
        unsigned long line_number;
        struct pkg_property *plist; /* PList root. */
        struct pkg_property *dirrm_list;
        struct pkg_property *unexec_list;
};

/* Parser delegators. */
static int _freebsd_plist_parse(struct pkg *, char *, const char *);
static int _freebsd_plist_parse_fd(struct pkg *, int, size_t, const char *);
static int _freebsd_plist_parse_FILE(struct pkg *, FILE *, const char *);

/* Writer delegators. */
static int _freebsd_plist_write(struct pkg *, char **, const char *);
static int _freebsd_plist_write_fd(struct pkg *, int, const char *);
static int _freebsd_plist_write_FILE(struct pkg *, FILE *, const char *);

/* CONTENTS parsers functions. */
static int _freebsd_plist_contents_parse(struct pkg_property *pl, char *buff);
/* XXX: DNE */
#if 0
static int _freebsd_plist_contents_parse_fd(struct pkg_property *pl, int fd, 
        size_t len);
#endif
static int _freebsd_plist_contents_parse_FILE(struct pkg_property *pl, FILE *);
static int _freebsd_plist_parse_contents_line(const char *, struct pkg_property *, 
        struct plist_parse_state *);
static inline int _freebsd_plist_parse_contents_command(struct pkg_property *,
        struct plist_parse_state *, const char *, const char *);
static int _freebsd_plist_parse_contents_option(struct pkg_property *, 
        struct plist_parse_state *, const char *);
static int _freebsd_plist_parse_contents_comment(struct pkg_property *,
        struct plist_parse_state *, const char *);
/* Initializer for the contents parse state. */
static void _freebsd_plist_parse_contents_init(struct plist_parse_state *, 
        struct pkg_property *);

/* REQUIRED_BY parsers functions. */
static int _freebsd_plist_required_by_parse(struct pkg_property *pl, char *);
/* XXX: DNE */
#if 0
static int _freebsd_plist_required_by_parse_fd(struct pkg_property *, int, 
        size_t);
#endif
static int _freebsd_plist_required_by_parse_FILE(struct pkg_property *, FILE *);

/* Blob 'parsers' for all other meta information.  These just read/copy 
 * information into PLSTRING property nodes. */
static int _freebsd_plist_blob_parse(struct pkg_property *, char *, const char *);
static int _freebsd_plist_blob_parse_fd(struct pkg_property *, int, size_t, 
        const char *);
static int _freebsd_plist_blob_parse_FILE(struct pkg_property *, FILE *, 
        const char *);

/* Contents writer. */
static int _freebsd_plist_contents_write_FILE(struct pkg_property *, FILE *);
/* Write contents line. */
static void _freebsd_plist_contents_write_command(FILE *fp, const char *,
        const char *);
static void _freebsd_plist_contents_write_comment(FILE *fp, const char *,
        const char *);
// static void _freebsd_plist_contents_write_files(FILE *, struct pkg_property *);
static int _freebsd_plist_required_by_write_FILE(struct pkg_property *, FILE *);
static int _freebsd_plist_blob_write_FILE(struct pkg_property *, FILE *,
        const char *);
static int _freebsd_plist_blob_write_fd(struct pkg_property *, int,
        const char *);

/*
 * Returns the static parser_writer_vtable for freebsd_plist's parsers and
 * writers.
 */
struct parser_writer_vtable *
freebsd_plist_parser_writer_vtable(void) {
        static struct parser_writer_vtable vt;

        if (vt.parse_FILE == NULL) {
                vt.parse_FILE = _freebsd_plist_parse_FILE;
                vt.parse_fd = _freebsd_plist_parse_fd;
                vt.parse = _freebsd_plist_parse;
                vt.write_FILE = _freebsd_plist_write_FILE;
                vt.write_fd = _freebsd_plist_write_fd;
                vt.write = _freebsd_plist_write;
        }
        return (&vt);
}

struct meta_entry *
freebsd_plist_meta_entry_table(void) {
        static struct meta_entry *me;
        static struct meta_entry meta_entries[] = {
                { PARSEABLE, FBSD_METACODE_CONTENTS, FBSD_METANAME_CONTENTS },
                { PARSEABLE, FBSD_METACODE_COMMENT, FBSD_METANAME_COMMENT },
                { PARSEABLE, FBSD_METACODE_DESCRIPTION, 
                        FBSD_METANAME_DESCRIPTION },
                { PARSEABLE, FBSD_METACODE_DISPLAY, FBSD_METANAME_DISPLAY },
                { PARSEABLE, FBSD_METACODE_REQUIRED_BY, 
                        FBSD_METANAME_REQUIRED_BY },
                { SCRIPT, FBSD_METACODE_PRE_INSTALL, 
                        FBSD_METANAME_PRE_INSTALL },
                { SCRIPT, FBSD_METACODE_POST_INSTALL,
                        FBSD_METANAME_POST_INSTALL },
                { SCRIPT, FBSD_METACODE_PRE_DEINSTALL,
                        FBSD_METANAME_PRE_DEINSTALL },
                { SCRIPT, FBSD_METACODE_POST_DEINSTALL,
                        FBSD_METANAME_POST_DEINSTALL },
                { 0x00000000, 0x00000000, NULL },
        };
        me = meta_entries;
        return (me);
}

/* PARSE */

static int
_freebsd_plist_parse(struct pkg *p, char *text, const char *metaname)
{
        struct pkg_property *pl;

        pl = pkg_get_property_list(p);
        switch (metaname[0]) {
        case ('C'):
        case ('R'):
                if (STRMATCH(metaname, "CONTENTS")) {
                        return (_freebsd_plist_contents_parse(pl, text));
                } else if (STRMATCH(metaname, "REQUIRED_BY")) {
                        return (_freebsd_plist_required_by_parse(pl, text));
                }
                /* FALLTHROUGH */
        default:
                return (_freebsd_plist_blob_parse(pl, text, metaname));
        }
        
        /* NOT REACHED. */
        return (PKG_NOT_OK);
}

static int
_freebsd_plist_parse_fd(struct pkg *p, int fd, size_t len, const char *metaname)
{
        FILE *fp;
        struct pkg_property *pl;
        int r;
        
        /* We call pkg_get_plist here so that we are sure to have a plist. */
        pl = pkg_get_property_list(p);
        switch (metaname[0]) {
        /* There's actually only two meta types that need parsing, the rest can
         * just be read in as blobs.  We open file streams with the file
         * descriptors we get in both parse cases so that we don't have to
         * allocate, read, and then deallocate the entire file. */
        case ('C'):
        case ('R'):
                /* This block puts me on life tilt... */
                if (STRMATCH(metaname, "CONTENTS")) {
                        fp = fdopen(fd, "r");
                        r = _freebsd_plist_contents_parse_FILE(pl, fp);
                        fclose(fp);
                        return (r);
                } else if (STRMATCH(metaname, "REQUIRED_BY")) {
                        fp = fdopen(fd, "r");
                        r = _freebsd_plist_required_by_parse_FILE(pl, fp);
                        fclose(fp);
                        return (r);
                }
                /* FALLTHROUGH */
        default:
                return (_freebsd_plist_blob_parse_fd(pl, fd, len, metaname));
        }
        
        /* NOT REACHED */
        return (PKG_NOT_OK);
}

static int
_freebsd_plist_parse_FILE(struct pkg *p, FILE *fp, const char *metaname)
{
        struct pkg_property *pl;
        
        pl = pkg_get_property_list(p);
        switch (metaname[0]) {
        case ('C'):
        case ('R'):
                if (STRMATCH(metaname, "CONTENTS")) {
                        return (_freebsd_plist_contents_parse_FILE(pl, fp));
                } else if (STRMATCH(metaname, "REQUIRED_BY")) {
                        return (_freebsd_plist_required_by_parse_FILE(pl, fp));
                }
                /* FALLTHROUGH */
        default:
                return (_freebsd_plist_blob_parse_FILE(pl, fp, metaname));
        }

        /* NOT REACHED */
        return (PKG_NOT_OK);
}

#define PLDICT_GET_OR_CREATE_LIST_PROPERTY(root, new, pname)            \
if ((new = pkg_property_dict_get_property(root, pname)) == NULL) {      \
        new = pkg_property_list_create(root, pname);                    \
}                                                                       \

/* Initialize a parse state structure and property list for parsing CONTENTS.
 * We take care of creating the different lists in here so that we don't have to
 * check for them everytime we want to add a property. */
static void
_freebsd_plist_parse_contents_init(struct plist_parse_state *ps,
        struct pkg_property *pl)
{
        ps->conflict_list = NULL;
        ps->current_p = NULL;
        ps->depend_list = NULL;
        ps->file_list = NULL;
        ps->ignore = 0;
        ps->line_number = 0;
        ps->last = PLIST_NONE;
        ps->plist = pl;
        
        strcpy(ps->cwd, CWD_DEFAULT);
        PLDICT_GET_OR_CREATE_LIST_PROPERTY(ps->plist, ps->conflict_list,
                FBSD_CONFLICTS);
        PLDICT_GET_OR_CREATE_LIST_PROPERTY(ps->plist, ps->depend_list,
                FBSD_DEPENDENCIES);
        PLDICT_GET_OR_CREATE_LIST_PROPERTY(ps->plist, ps->file_list,
                FBSD_FILES);
        PLDICT_GET_OR_CREATE_LIST_PROPERTY(ps->plist, ps->exec_list,
                FBSD_EXECS);
        PLDICT_GET_OR_CREATE_LIST_PROPERTY(ps->plist, ps->unexec_list,
                FBSD_UNEXECS);
        PLDICT_GET_OR_CREATE_LIST_PROPERTY(ps->plist, ps->dirrm_list,
                FBSD_DIRRMS);
}

static int
_freebsd_plist_contents_parse(struct pkg_property *pl, char *buff)
{
	char *line;
	struct plist_parse_state ps;

        _freebsd_plist_parse_contents_init(&ps, pl);
	while ((line = strsep(&buff, "\n")) != NULL) {
                if (_freebsd_plist_parse_contents_line(line, pl, &ps) != 
                        PKG_OK) {
                        PKG_CLIENT_WARNING("parse failed.");
                        return (PKG_NOT_OK);
                }
	}

        return (PKG_OK);
}

static int
_freebsd_plist_contents_parse_FILE(struct pkg_property *pl, FILE *fp)
{
        char line[LINE_MAX];
        struct plist_parse_state ps;
        int r;

        _freebsd_plist_parse_contents_init(&ps, pl);
        while (fgets(line, sizeof(line), fp) != NULL) {
                _pkg_util_trim_newline(line);
                if ((r = _freebsd_plist_parse_contents_line(line, pl, &ps)) !=
                        PKG_OK) {
                        warnx("%s: unknown comand '%s' (libpkg out of data?)",
                        __func__, line);
                        if (r == PKG_FATAL) {
                                PKG_CLIENT_WARNING("parse failed");
                                return (r);
                        }
                }
        }
        return (PKG_OK);
}

/* XXX: These DO NOT check *anything*. */

/* Avoid duplicate plist entries. */
#define ADD_SET_STRING_PROPERTY(root, new, pname, pvalue)       \
new = pkg_property_dict_get_property(root, pname);              \
if (new == NULL) {                                              \
        new = pkg_property_string_create(root, pname);          \
}                                                               \
pkg_property_string_set_string(new, pvalue)

#define ADD_BOOLEAN_PROPERTY(root, new, pname, pvalue)          \
new = pkg_property_boolean_create(root, pname);                 \
if (pvalue) {                                                   \
        pkg_property_boolean_set_true(new);                     \
} else {                                                        \
        pkg_property_boolean_set_false(new);                    \
}

#define ADD_SET_DICT_PROPERTY(root, new, pname)                 \
new = pkg_property_dict_create(root, pname)

/* Append a string property to a list. */
#define APPEND_SET_STRING_PROPERTY(root, new, pname, pvalue)    \
new = pkg_property_string_create(root, pname);                  \
pkg_property_string_set_string(new, pvalue)

static int
_freebsd_plist_parse_contents_line(const char *line, struct pkg_property *pl,
        struct plist_parse_state *ps)
{
        const char *arg;
        char *cmdp;
        char cmd[LINE_MAX];
        struct pkg_property *p;
        
        /* Instead of using sscanf() [SLOW AS BALLS], use this method from
         * plist.c in the current pkg_install tools. */
        strcpy(cmd, line);
        cmdp = cmd;
        arg = line;

        while (*cmdp) {
                if (isspace(*cmdp)) {
                        *cmdp = '\0';
                        while (isspace(*arg)) {
                                ++arg;
                        }
                        break;
                }
                ++cmdp; ++arg;
        }

        switch (cmd[0]) {
        case (CMD_PREFIX):
                if (_freebsd_plist_parse_contents_command(pl, ps, 
                        cmd + 1, arg)!= PKG_OK) {
                        return (PKG_NOT_OK);
                }
                break;
        default:
                /* Now, we create a file dictionary node in the file list. */
                ps->current_p = pkg_property_dict_create(ps->file_list, 
                        FBSD_FILE);
                if (ps->current_p == NULL) {
                        PKG_CLIENT_WARNING("allocation error");
                        return (PKG_NOT_OK);
                }
                /* Add whatever file properties we can from the parse state. */
                ADD_SET_STRING_PROPERTY(ps->current_p, p, FBSD_PATHNAME, cmd);
                ADD_SET_STRING_PROPERTY(ps->current_p, p, FBSD_PREFIX, ps->cwd);
                ADD_BOOLEAN_PROPERTY(ps->current_p, p, FBSD_IGNORE, ps->ignore);
                /* Reset ignore. */
                ps->ignore = 0;
                ps->last = PLIST_FILE;
                break;
        }

        return (PKG_OK);
}

static int
_freebsd_plist_parse_contents_command(struct pkg_property *pl, 
        struct plist_parse_state *ps, const char *command, const char *argument)
{
        struct pkg_property *pn;
        struct pkg_property *nn;

        /* Use two levels of switching to try and narrow the command
         * possibilities down before do a complete strcmp. */
        switch (command[0]) {
        case ('c'):
                switch (command[1]) {
                case ('o'):
                        if (STRMATCH(command, CMD_COMMENT)) {
                                /* @comment */
                                return (_freebsd_plist_parse_contents_comment(
                                        pl, ps, argument));
                        } else if (STRMATCH(command, CMD_CONFLICTS)) {
                                /* @conflicts */
                                /* I'd really like this to become a PLDICT at
                                 * some point, but right now it's a waste. */
                                APPEND_SET_STRING_PROPERTY(ps->conflict_list, 
                                        nn, FBSD_CONFLICT, argument);
                                ps->last = PLIST_CONFLICT;
                                return (PKG_OK);
                        }
                        break;
                default:
                        if (STRMATCH(command, CMD_CWD) ||
                                STRMATCH(command, CMD_CD)) {
                                /* @cwd @cd */
                                strcpy(ps->cwd, argument);
                                return (PKG_OK);
                        }
                        break;
                }
                break;
        case ('d'):
                switch (command[1]) {
                case ('i'):
                        if (STRMATCH(command, CMD_DIRRM)) {
                                /* @dirrm */
                                APPEND_SET_STRING_PROPERTY(ps->dirrm_list, 
                                        nn, FBSD_DIRRM, argument);
                                return (PKG_OK);
                        } else if (STRMATCH(command, CMD_DISPLAY)) {
                                /* @display */
                                /* UNIMPLEMENTED */
                                warnx("@display parse not implemented");
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('e'):
                switch (command[1]) {
                case ('x'):
                        if (STRMATCH(command, CMD_EXEC)) {
                                /* @exec */
                                ADD_SET_DICT_PROPERTY(ps->exec_list,
                                        ps->current_p, FBSD_EXECS);
                                ADD_SET_STRING_PROPERTY(ps->current_p, nn,
                                        FBSD_COMMAND, argument);
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('g'):
                switch (command[1]) {
                case ('r'):
                        if (STRMATCH(command, CMD_GROUP)) {
                                /* @group */
                                /* DROP SUPPORT. */
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('i'):
                switch (command[1]) {
                case ('g'):
                        if (STRMATCH(command, CMD_IGNORE)) {
                                /* @ignore */
                                ps->ignore = 1;
                                return (PKG_OK);
                        } else if (STRMATCH(command, CMD_IGNORE_INST)) {
                                /* @ignore_inst */
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('m'):
                switch (command[1]) {
                case ('o'):
                        if (STRMATCH(command, CMD_MODE)) {
                                /* DROP SUPPORT. */
                                return (PKG_OK);
                        }
                        break;
                case ('t'):
                        if (STRMATCH(command, CMD_MTREE)) {
                                /* @mtree */
                                /* UNIMPLEMENTED */
                                warnx("@mtree parse not implemented");
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('n'):
                switch (command[1]) {
                case ('a'):
                        if (STRMATCH(command, CMD_NAME)) {
                                /* @name */
                                ADD_SET_STRING_PROPERTY(pl, pn, FBSD_NAME, 
                                        argument);
                                return (PKG_OK);
                        }
                        break;
                case ('o'):
                        if (STRMATCH(command, CMD_NOINST)) {
                                /* @noinst */
                                /* UNIMPLEMENTED */
                                warnx("@noinst parse not implemented");
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('o'):
                switch (command[1]) {
                case ('p'):
                        if (STRMATCH(command, CMD_OPTION)) {
                                /* @option */
                                return (_freebsd_plist_parse_contents_option(pl,
                                        ps, argument));
                        }
                        break;
                case ('w'):
                        if (STRMATCH(command, CMD_OWNER)) {
                                /* DROP SUPPORT. */
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('p'):
                switch (command[1]) {
                case ('k'):
                        if (STRMATCH(command, CMD_PKGDEP)) {
                                /* @pkgdep */
                                ADD_SET_DICT_PROPERTY(ps->depend_list, 
                                        ps->current_p, FBSD_DEPENDENCY);
                                ADD_SET_STRING_PROPERTY(ps->current_p, nn, 
                                        FBSD_NAME, argument);
                                ps->last = PLIST_DEPENDENCY;
                                return (PKG_OK);
                        } else if (STRMATCH(command, CMD_PKGVERSION)) {
                                /* @pkgversion */
                                /* UNUSED. */
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('s'):
                switch (command[1]) {
                case ('r'):
                        if (STRMATCH(command, CMD_SRCDIR)) {
                                /* @srcdir */
                                ADD_SET_STRING_PROPERTY(pl, pn, FBSD_SRCDIR,
                                        argument);
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        case ('u'):
                switch (command[1]) {
                case ('n'):
                        if (STRMATCH(command, CMD_UNEXEC)) {
                                /* @unxec */
                                ADD_SET_DICT_PROPERTY(ps->unexec_list,
                                        ps->current_p, FBSD_UNEXEC);
                                ADD_SET_STRING_PROPERTY(ps->current_p, nn,
                                        FBSD_COMMAND, argument);
                                return (PKG_OK);
                        }
                        break;
                default:
                        break;
                }
                break;
        default:
                break;
        }

        /* Unrecognized command. */

        return (PKG_NOT_OK);
}

static int
_freebsd_plist_parse_contents_comment(struct pkg_property *pl,
        struct plist_parse_state *ps, const char *comment)
{
        /* XXX: Apparently this function is very, very, very slow. */
        struct pkg_property *pn;
        
        switch (comment[0]) {
        case ('D'):
                if (strncmp(comment, CMT_DEPORIGIN, 10) == 0) {
                        if (ps->last == PLIST_DEPENDENCY) {
                                ADD_SET_STRING_PROPERTY(ps->current_p, pn,
                                        FBSD_ORIGIN, 
                                        comment + 10);
                        } else {
                                /* It would be great to be able to set an error
                                 * string here, rather than warning straight
                                 * up... */
                                /* XXX: NOT A CLIENT_WARNING, keep it clean. */
                                warnx("Plist: Misplaced DEPORIGIN meta comment"
                                " [line %ld: %s]", ps->line_number, comment);
                                return (PKG_NOT_OK);
                        }
                }
                break;
        case ('O'):
                if (strncmp(comment, CMT_ORIGIN, 7) == 0) {
                        ADD_SET_STRING_PROPERTY(pl, pn, FBSD_ORIGIN,
                                comment + 7);
                }
                break;
        case ('P'):
                if (strncmp(comment, CMT_PKG_FORMAT_REVISION, 20) == 0) {
                        ADD_SET_STRING_PROPERTY(pl, pn, FBSD_FORMAT_REVISION,
                                comment + 20);
                }
                break;
        case ('M'):
                if (strncmp(comment, CMT_MD5, 4) == 0) {
                        if (ps->last == PLIST_FILE) {
                                ADD_SET_STRING_PROPERTY(ps->current_p, pn,
                                        FBSD_HASH,
                                        comment + 4);
                        } else {
                                warnx("Plist: Misplaced MD5 meta comment"
                                " [line %ld: %s]", ps->line_number, comment);
                                return (PKG_NOT_OK);
                        }
                }
                break;
        default:
                break;
        }

        return (PKG_OK);
}

static inline int
_freebsd_plist_parse_contents_option(struct pkg_property *pl, 
        struct plist_parse_state *ps, const char *argument)
{
        (void)pl;
        (void)ps;
        (void)argument;
        return (PKG_OK);
}

static int
_freebsd_plist_required_by_parse(struct pkg_property *pl, char *text)
{
        (void)pl;
        (void)text;

        PKG_CLIENT_CRASH(__func__, "Not implemented.");
        
        return (PKG_NOT_OK);
}

static int
_freebsd_plist_required_by_parse_FILE(struct pkg_property *pl, FILE *fp)
{
        char line[PATH_MAX];
        struct pkg_property *pn;
        struct pkg_property *en;

        if ((pn = pkg_property_dict_get_property(pl, 
                FBSD_METANAME_REQUIRED_BY)) == NULL) {
                /* Use the metaname. */
                pn = pkg_property_list_create(pl, FBSD_METANAME_REQUIRED_BY);
                if (pn == NULL) {
                        return (PKG_NOT_OK);
                }
        } else if (pkg_property_get_type(pn) != PLLIST) {
                PKG_CLIENT_WARNING("Required by node is not a PLLIST");
                return (PKG_FATAL);
        }

        while (fgets(line, sizeof(line), fp) != NULL) {
                _pkg_util_trim_newline(line);
                /* For now, these are just represented as string properties. */
                APPEND_SET_STRING_PROPERTY(pn, en, FBSD_REQUIRED_BY_ENTRY, 
                        line);
        }

        return (PKG_OK);
}

static int
_freebsd_plist_blob_parse(struct pkg_property *pl, char *text, 
        const char *metaname)
{
        struct pkg_property *pn;

        if ((pn = pkg_property_dict_get_property(pl, metaname)) == NULL) {
                pn = pkg_property_data_create(pl, metaname);
                if (pn == NULL) {
                        return (PKG_NOT_OK);
                }
        } else if (pkg_property_get_type(pn) != PLDATA) {
                PKG_CLIENT_WARNING("node is not a PLDATA");
                return (PKG_FATAL);
        }
        if (pkg_property_data_set_data(pn, text, strlen(text)) == NULL) {
                return (PKG_NOT_OK);
        }
        return (PKG_OK);
}

static int
_freebsd_plist_blob_parse_fd(struct pkg_property *pl, int fd, size_t len, 
        const char *metaname)
{
        struct pkg_property *pn;

        if ((pn = pkg_property_dict_get_property(pl, metaname)) == NULL) {
                pn = pkg_property_data_create(pl, metaname);
                if (pn == NULL) {
                        return (PKG_NOT_OK);
                }
        }
        /* Read directory from the descriptor. */
        if (pkg_property_data_read_data(pn, fd, len) == NULL) {
                return (PKG_NOT_OK);
        }
        return (PKG_OK);
}

static int
_freebsd_plist_blob_parse_FILE(struct pkg_property *pl, FILE *fp,
        const char *metaname)
{
        int fd;
        struct stat sb;
        
        /* 
         * Eh, this sort of works.  Apparently fseek & ftell have undefined
         * behavior, so I can go that route.
         */
        fd = fileno(fp);
        if (fstat(fd, &sb) < 0) {
                return (PKG_NOT_OK);
        }
        return (_freebsd_plist_blob_parse_fd(pl, fd, sb.st_size, metaname));
}

/* WRITE */

static int
_freebsd_plist_write(struct pkg *p, char **buff, const char *metaname)
{
        (void)p;
        (void)buff;
        (void)metaname;
        
        PKG_CLIENT_CRASH(__func__, "Not implemented.");

        return (PKG_NOT_OK);
}

static int
_freebsd_plist_write_fd(struct pkg *p, int fd, const char *metaname)
{
        (void)p;
        (void)fd;
        (void)metaname;

        PKG_CLIENT_CRASH(__func__, "Not implemented.");

        return (PKG_NOT_OK);
}

static int
_freebsd_plist_write_FILE(struct pkg *p, FILE *fp, const char *metaname)
{
        (void)p;
        (void)fp;
        (void)metaname;

        struct pkg_property *pl;

        pl = pkg_get_property_list(p);
        if (STRMATCH(metaname, FBSD_METANAME_CONTENTS)) {
                _freebsd_plist_contents_write_FILE(pl, fp);
        } else if (STRMATCH(metaname, FBSD_METANAME_REQUIRED_BY)) {
                _freebsd_plist_required_by_write_FILE(pl, fp);
        } else {
                _freebsd_plist_blob_write_FILE(pl, fp, metaname);
        }
        return (PKG_NOT_OK);
}

/*
 * Write a FreeBSD +CONTENTS file to fp.  For now, we'll crash if we have a
 * badly built property list.
 */
static int
_freebsd_plist_contents_write_FILE(struct pkg_property *pl, FILE *fp)
{
        const char *cwd;
        const char *cwd_b;
        struct pkg_property *pn;
        struct pkg_property *pp;
        struct pkg_property *pb;
        int r; /* To check write functions. */
        
        pn = NULL;
        pp = NULL;
        pb = NULL;
        cwd = NULL;
        (void)r;
        
        /*TODO(?): maybe a write_state? */

        /* 
         * TODO: These writes need to be checked.  If they're bad, we're dealing
         * with corrupt package data, and that's not fun.
         */

        /* No prefix yet. */
        cwd = NULL;

        /* Format revision. */
        pn = pkg_property_dict_get_property(pl, FBSD_FORMAT_REVISION);
        if (pn != NULL) {
                /* 
                 * It's not particularly terrible if this is missing.  Mainly
                 * because we don't care about it.
                 */
                _freebsd_plist_contents_write_comment(fp,
                CMT_PKG_FORMAT_REVISION,
                pkg_property_string_get_string(pn));
        }
        
        /* Package name. */
        pn = pkg_property_dict_get_property(pl, FBSD_NAME);
        if (pn == NULL) {
                /* 
                 * We can just make this a format requirement.  No-name, no
                 * good.  I should write a validation function if that's the
                 * case.
                 */
                return (PKG_FATAL);
        }
        _freebsd_plist_contents_write_command(fp, CMD_NAME, 
                pkg_property_string_get_string(pn));

        /* Package origin. */
        pn = pkg_property_dict_get_property(pl, FBSD_ORIGIN);
        if (pn != NULL) {
                _freebsd_plist_contents_write_comment(fp, CMT_ORIGIN,
                pkg_property_string_get_string(pn));
        }
        
        /* Options. */
        /* 
         * The reason we don't just assume that an option is true just because
         * the node exist is that a client might actually set a value to false
         * manually.
         */
        pn = pkg_property_dict_get_property(pl, FBSD_PRESERVE);
        if (pn != NULL) {
                if (pkg_property_boolean_get_boolean(pn)) {
                        _freebsd_plist_contents_write_command(fp,
                        PLIST_OPTION_PRESERVE, "");
                }
        }
        pn = pkg_property_dict_get_property(pl, FBSD_EXTRACT_IN_PLACE);
        if (pn != NULL) {
                if (pkg_property_boolean_get_boolean(pn)) {
                        _freebsd_plist_contents_write_command(fp,
                        PLIST_OPTION_EXTRACT_IN_PLACE, "");
                }
        }

        /* Conflicts. */
        pn = pkg_property_dict_get_property(pl, FBSD_CONFLICTS);
        if (pn != NULL) {
                pp = NULL;
                while ((pp = pkg_property_list_get_next_property(pn, pp)) !=
                        NULL) {
                        _freebsd_plist_contents_write_command(fp, CMD_CONFLICTS,
                        pkg_property_string_get_string(pp));
                }
        }

        /* Dependencies. */
        pn = pkg_property_dict_get_property(pl, FBSD_DEPENDENCIES);
        if (pn != NULL) {
                pp = NULL;
                while ((pp = pkg_property_list_get_next_property(pn, pp)) !=
                        NULL) {
                        pb = pkg_property_dict_get_property(pp,
                                FBSD_NAME);
                        if (pb == NULL) {
                                PKG_CLIENT_CRASH(__func__, "Dependency with no "
                                "name.");
                        }
                        _freebsd_plist_contents_write_command(fp, CMD_PKGDEP,
                        pkg_property_string_get_string(pb));

                        pb = pkg_property_dict_get_property(pp, FBSD_ORIGIN);
                        if (pb != NULL) {
                                _freebsd_plist_contents_write_comment(fp, 
                                CMT_DEPORIGIN,
                                pkg_property_string_get_string(pb));
                        }
                }
        }

        /* Files. */
        pn = pkg_property_dict_get_property(pl, FBSD_FILES);
        if (pn != NULL) {
                pp = NULL;
                while ((pp = pkg_property_list_get_next_property(pn, pp)) !=
                        NULL) {
                        /* Ignore. */
                        pb = pkg_property_dict_get_property(pp, FBSD_IGNORE);
                        if (pb != NULL) {
                                /* Not required. */

                                /* Still need to check. */
                                if (pkg_property_boolean_get_boolean(pb)) {
                                        _freebsd_plist_contents_write_command(
                                        fp, CMD_IGNORE, "");
                                }
                        }
                        /* Prefix. */
                        pb = pkg_property_dict_get_property(pp, FBSD_PREFIX);
                        if (pb == NULL) {
                                /* Client forgot to set a prefix for the file
                                 * (we set default in the parser).  Set it to
                                 * default for them. */
                                cwd_b = CWD_DEFAULT;
                        } else {
                                cwd_b = pkg_property_string_get_string(pb);
                        }
                        if (cwd == NULL || !STRMATCH(cwd, cwd_b)) {
                                /* cwd change.  Write the command. */
                                cwd = cwd_b;
                                _freebsd_plist_contents_write_command(fp,
                                CMD_CWD, cwd);
                        }
                        /* Pathname. */
                        pb = pkg_property_dict_get_property(pp, FBSD_PATHNAME);
                        if (pb == NULL) {
                                /* No pathname?  What? */
                                PKG_CLIENT_CRASH(__func__, "File with no "
                                "pathname.");
                        }
                        fprintf(fp, "%s\n", pkg_property_string_get_string(pb));
                        /* Hash. */
                        pb = pkg_property_dict_get_property(pp, FBSD_HASH);
                        if (pb != NULL) {
                                /* Not required. */
                                _freebsd_plist_contents_write_comment(fp,
                                CMT_MD5, pkg_property_string_get_string(pb));
                        }
                }
        }
        
        /* Inst. */
        pn = pkg_property_dict_get_property(pl, FBSD_EXECS);
        if (pn != NULL) {
                pp = NULL;
                while ((pp = pkg_property_list_get_next_property(pn, pp)) !=
                        NULL) {
                        /* Prefix. */
                        /* XXX: Getting bogus "@cwd ." lines out of this.  Check
                         * parser to make sure exec is getting a prefix set. */
                        pb = pkg_property_dict_get_property(pp, FBSD_PREFIX);
                        if (pb == NULL) {
                                /* Client forgot to set a prefix for the file
                                 * (we set default in the parser).  Set it to
                                 * default for them. */
                                cwd_b = CWD_DEFAULT;
                        } else {
                                cwd_b = pkg_property_string_get_string(pb);
                        }
                        if (cwd == NULL || !STRMATCH(cwd, cwd_b)) {
                                /* cwd change.  Write the command. */
                                cwd = cwd_b;
                                _freebsd_plist_contents_write_command(fp,
                                CMD_CWD, cwd);
                        }
                        /* Command. */
                        pb = pkg_property_dict_get_property(pp, FBSD_COMMAND);
                        _freebsd_plist_contents_write_command(fp, CMD_EXEC,
                                pkg_property_string_get_string(pb));
                }
        }
        pn = pkg_property_dict_get_property(pl, FBSD_UNEXECS);
        if (pn != NULL) {
                pp = NULL;
                while ((pp = pkg_property_list_get_next_property(pn, pp)) !=
                        NULL) {
                        /* Prefix. */
                        /* XXX: Getting bogus "@cwd ." lines out of this.  Check
                         * parser to make sure exec is getting a prefix set. */
                        pb = pkg_property_dict_get_property(pp, FBSD_PREFIX);
                        if (pb == NULL) {
                                /* Client forgot to set a prefix for the file
                                 * (we set default in the parser).  Set it to
                                 * default for them. */
                                cwd_b = CWD_DEFAULT;
                        } else {
                                cwd_b = pkg_property_string_get_string(pb);
                        }
                        if (cwd == NULL || !STRMATCH(cwd, cwd_b)) {
                                /* cwd change.  Write the command. */
                                cwd = cwd_b;
                                _freebsd_plist_contents_write_command(fp,
                                CMD_CWD, cwd);
                        }
                        pb = pkg_property_dict_get_property(pp, FBSD_COMMAND);
                        _freebsd_plist_contents_write_command(fp, CMD_UNEXEC,
                                pkg_property_string_get_string(pb));
                }
        }

        /* Directory removal. */
        pn = pkg_property_dict_get_property(pl, FBSD_DIRRMS);
        if (pn != NULL) {
                pp = NULL;
                while ((pp = pkg_property_list_get_next_property(pn, pp)) !=
                        NULL) {
                        /* XXX: Dirrm needs a prefix node! */
                        _freebsd_plist_contents_write_command(fp, CMD_DIRRM,
                                pkg_property_string_get_string(pp));
                }
        }
        
        return (PKG_OK);
}

static void
_freebsd_plist_contents_write_command(FILE *fp, const char *c, const char *val)
{
        if (c == NULL) {
                PKG_CLIENT_CRASH(__func__, "Trying to write NULL command.");
        }
        fprintf(fp, "%c%s %s\n", CMD_PREFIX, c, (val == NULL ? "" : val));
}

static void
_freebsd_plist_contents_write_comment(FILE *fp, const char *meta,
        const char *val)
{
        const char *m;
        m = (meta == NULL ? "" : meta);
        fprintf(fp, "%c%s %s%s\n", CMD_PREFIX, CMD_COMMENT, m, val);
}

#if 0
static void
_freebsd_plist_contents_write_files(FILE *fp, struct pkg_property *pn,
        const char *cwd)
{
        struct pkg_property *pp;
        struct pkg_property *ps;

        pkg_property_list_sequence_rewind(pn);
        while ((pp = pkg_property_list_sequence_get_next_property(pn)) != NULL)
                {
                ps = pkg_property_dict_get_property(pp, FBSD_PATHNAME);
                if (ps == NULL) {
                        /* A file has to have a pathname. */
                        PKG_CLIENT_CRASH(__func__, "File has no pathname.");
                }
                fprintf(fp, "%s\n", pkg_property_string_get_string(ps));
                
                ps = pkg_property_dict_get_property(pp, FBSD_HASH);
                if (ps != NULL) {
                        _freebsd_plist_contents_write_comment(fp, CMT_MD5,
                                pkg_property_string_get_string(ps));
                }
        }
}
#endif

static int
_freebsd_plist_required_by_write_FILE(struct pkg_property *pl, FILE *fp)
{
        struct pkg_property *pn;
        struct pkg_property *pp;

        pn = pkg_property_dict_get_property(pl, FBSD_METANAME_REQUIRED_BY);
        if (pn == NULL) {
                return (PKG_OK);
        }
        pp = NULL;
        while ((pp = pkg_property_list_get_next_property(pn, pp)) != NULL) {
                if (pp != NULL) {
                        fprintf(fp, "%s\n", pkg_property_string_get_string(pp));
                }
        }
        return (PKG_OK);
}

static int
_freebsd_plist_blob_write_fd(struct pkg_property *pl, int fd, 
        const char *metaname)
{
        struct pkg_property *pn;
        const void *data;
        ssize_t s;
        ssize_t w;

        pn = pkg_property_dict_get_property(pl, metaname);
        if (pn == NULL) {
                return (PKG_OK);
        }
        s = pkg_property_data_get_data(pn, &data);
        if (s < 0) {
                return (PKG_NOT_OK);
        }
        w = write(fd, data, s);
        if (w < s) {
                return (PKG_NOT_OK);
        }
        return (PKG_OK);
}

static int
_freebsd_plist_blob_write_FILE(struct pkg_property *pl, FILE *fp,
        const char *metaname)
{
        int fd;
        fd = fileno(fp);
        return (_freebsd_plist_blob_write_fd(pl, fd, metaname));
}
